Android硬件位图填坑之获取硬件画布 您所在的位置:网站首页 android rendernode Android硬件位图填坑之获取硬件画布

Android硬件位图填坑之获取硬件画布

#Android硬件位图填坑之获取硬件画布| 来源: 网络整理| 查看: 265

前言

Hardware Bitmap(硬件位图)是Android8.0加入的新功能,通过设置Bitmap的config为Bitmap.Config.HARDWARE,创建所谓的Hardware Bitmap,它不同与其他Config的Bitmap,Hardware Bitmap对应的像素数据是存储在显存中,并对图片仅在屏幕上绘制的场景做了优化;

硬件位图的介绍参考Glide文档

何如使用Hardware Bitmap 创建Hardware Bitmap

众所周知,Bitmap的创建一般是调用BitmapFactory这个工厂类来实现,由于Hardware Bitmap需要配置Bitmap.Config.HARDWARE属性,一个基本的获取用Hardware Bitmap的写法如下:

val options = BitmapFactory.Options() options.inPreferredConfig = Bitmap.Config.HARDWARE val bitmap = BitmapFactory.decodeResource(resources, R.drawable.dog, options) 复制代码

主要是需要设置BitmapFactory.Options的inPreferredConfig为Bitmap.Config.HARDWARE;

针对HARDWARE情况BitmapFactory的提示

如果设置了inPreferredConfig = Bitmap.Config.HARDWARE,千万不要设置options.inMutable = true,这样会引起报错,因为Hardware Bitmap是不可变的,也不能被利用;另外inBitmap属性也没有必要设置,因为硬件位图不需要当前进程的缓存复用,如果设置inBitmap可能会替换掉之前设置的inPreferredConfig属性;

使用Hardware Bitmap

通过上一步的创建,我们获得Bitmap对象,首先我们可以通过bitmap.getConfig()获取到当前Bitmap是不是Hardware,其次,大多数情况下,我们是把Bitmap设置给ImageView控件;

imageView.setImageBitmap(bitmap) 复制代码

一行代码搞定imageView没错,这行代码一般情况下是没有问题的,那么问题在哪里?

首先,硬件位图只支持GPU的绘制,言外之意是这个ImageView必须在开启硬件加速的Activity中,而且当前这个ImageView不能设置软件层 (software layer type);

开启硬件加速的代码 //application级别开启硬件加速 //activity级别开启硬件加速 复制代码 在View 上使用software layer type ImageView imageView = … imageView.setImageBitmap(hardwareBitmap); imageView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); 复制代码

如果我们满足硬件加速和不设置software layer type这两个条件,在正真使用中还有坑,其中最大的也最频繁发生的就是通过Canvas来改变Bitmap的形状或者其他的转换;

拿圆形图片做例子

假设我们需要显示圆形图片,一般解决方案有两种:通过自定义控件处理和通过Glide等工具类直接剪裁Bitmap;当Bitmap剪裁遇到HARDWARE就是问题的开始;

通过自定义控件比如CircleImageView的方案,onDraw()方法如下: @Override protected void onDraw(Canvas canvas) { if (mDisableCircularTransformation) { super.onDraw(canvas); return; } if (mBitmap == null) { return; } if (mCircleBackgroundColor != Color.TRANSPARENT) { canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mCircleBackgroundPaint); } canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mBitmapPaint); if (mBorderWidth > 0) { canvas.drawCircle(mBorderRect.centerX(), mBorderRect.centerY(), mBorderRadius, mBorderPaint); } } 复制代码

这是CircleImageView重新onDraw()方法,通过自定义控件实现剪切圆角,在设置硬件位图Bitmap时,一般都没有问题;

通过类似Glide等直接处理Bitmap的方式剪裁圆形图片,基本代码如下: Canvas canvas = new Canvas(resultBitmap); // Draw a circle canvas.drawCircle(radius, radius, radius, CIRCLE_CROP_SHAPE_PAINT); // Draw the bitmap in the circle canvas.drawBitmap(inBitmap, null, destRect, CIRCLE_CROP_BITMAP_PAINT); clear(canvas); 复制代码

通过类似工具类的形式直接对Bitmap进行修改,执行到canvas.drawBitmap就会报异常,异常信息是java.lang.IllegalStateException: Software rendering doesn't support hardware bitmaps;

如何避免报异常

我大致想了这么两个方案:

方案一:所有关于剪切Bitmap的操作都改成自定义控件,在自定义控件的onDraw中实现; 方案二:寻找一种方案,解决掉自己创建的Canvas不报异常,这样就能继续用工具类来处理Bitmap;

方案一技术实现比较简单,把项目中所用用到处理Bitmap的逻辑都换成自定义控件,但是可能涉及到很多处代码的修改,是一个功夫活;

方案二实施起来有点障碍,因为除了通过new Canvas(Bitmap)获取画布,还能通过什么方式能拿到Canvas,对了还有SurfaceView也是可以拿到Canvas,但是SurfaceView不支持硬件加速,所以直接就Pass了,想实现方案二我认为得弄清自定义控件onDraw()方法中Canvas从何而来;

分析Canvas流程

View.onDraw()中的Canvas从何而来

我们知道,View的绘制流程是从ViewRootImpl.performTraversals()这个方法开始

performTraversals()

boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible; if (!cancelDraw && !newSurface) { if (mPendingTransitions != null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).startChangingAnimations(); } mPendingTransitions.clear(); } //调动performDraw() performDraw(); } else { if (isViewVisible) { // Try again scheduleTraversals(); } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).endChangingAnimations(); } mPendingTransitions.clear(); } } 复制代码

performTraversals()方法调用performDraw(),然后performDraw()方法中又调用draw(fullRedrawNeeded),大部门绘制的逻辑都是在draw(fullRedrawNeeded)方法中;

draw(fullRedrawNeeded)

if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) { if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) { //省略代码 mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this); } else { if (mAttachInfo.mThreadedRenderer != null && !mAttachInfo.mThreadedRenderer.isEnabled() && mAttachInfo.mThreadedRenderer.isRequested()) { //省略代码 //drawSoftware if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { return; } } } 复制代码

从draw(fullRedrawNeeded)方法可以看到,如果支持硬件加速,调用mAttachInfo.mThreadedRenderer.draw()方法,否则调用drawSoftware()方法,绘制的基本流程从这里分叉;

drawSoftware如何获得Canvas

drawSoftware()

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { // Draw with software renderer. final Canvas canvas; try { final int left = dirty.left; final int top = dirty.top; final int right = dirty.right; final int bottom = dirty.bottom; canvas = mSurface.lockCanvas(dirty); Surface.lockCanvas() //noinspection ConstantConditions if (left != dirty.left || top != dirty.top || right != dirty.right || bottom != dirty.bottom) { attachInfo.mIgnoreDirtyState = true; } canvas.setDensity(mDensity); } catch (Surface.OutOfResourcesException e) { handleOutOfResourcesException(e); return false; } catch (IllegalArgumentException e) { mLayoutRequested = true; return false; } //省略代码 } 复制代码

从drawSoftware()方法可以知道,软件绘制的流程是从Surface.lockCanvas()获得Canvas对象;

View体系硬件加速Canvas创建过程

ThreadedRenderer.draw()

void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) { attachInfo.mIgnoreDirtyState = true; final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer; choreographer.mFrameInfo.markDrawStart(); //调用updateRootDisplayList更新DisplayList updateRootDisplayList(view, callbacks); } 复制代码

ThreadedRenderer.updateRootDisplayList()

private void updateRootDisplayList(View view, DrawCallbacks callbacks) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()"); updateViewTreeDisplayList(view); if (mRootNodeNeedsUpdate || !mRootNode.isValid()) { //通过RootNode.start创建DisplayListCanvas DisplayListCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight); try { final int saveCount = canvas.save(); canvas.translate(mInsetLeft, mInsetTop); callbacks.onPreDraw(canvas); canvas.insertReorderBarrier(); canvas.drawRenderNode(view.updateDisplayListIfDirty()); canvas.insertInorderBarrier(); callbacks.onPostDraw(canvas); canvas.restoreToCount(saveCount); mRootNodeNeedsUpdate = false; } finally { //最终调用end方法 mRootNode.end(canvas); } } Trace.traceEnd(Trace.TRACE_TAG_VIEW); } 复制代码

View.updateDisplayListIfDirty()

public RenderNode updateDisplayListIfDirty() { final RenderNode renderNode = mRenderNode; //省略代码 int layerType = getLayerType(); //创建DisplayListCanvas final DisplayListCanvas canvas = renderNode.start(width, height); canvas.setHighContrastText(mAttachInfo.mHighContrastText); try { //判断layerType if (layerType == LAYER_TYPE_SOFTWARE) { buildDrawingCache(true); Bitmap cache = getDrawingCache(true); if (cache != null) { canvas.drawBitmap(cache, 0, 0, mLayerPaint); } } else { computeScroll(); canvas.translate(-mScrollX, -mScrollY); mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; mPrivateFlags &= ~PFLAG_DIRTY_MASK; // Fast path for layouts with no backgrounds if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { dispatchDraw(canvas);//dispatchDraw drawAutofilledHighlight(canvas); if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().draw(canvas); } if (debugDraw()) { debugDrawFocus(canvas); } } else { //调用draw()方法 draw(canvas); } } } finally { renderNode.end(canvas); setDisplayListProperties(renderNode); } } else { mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; mPrivateFlags &= ~PFLAG_DIRTY_MASK; } return renderNode; } 复制代码

从上面基本流程可以看出,硬件加速下Canvas的创建是调用RenderNode.create()方法,每个View都有自己的RenderNode,RenderNode的创建是在View的构造方法中;

View构造方法

public View(Context context) { mContext = context; mResources = context != null ? context.getResources() : null; mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED | FOCUSABLE_AUTO; //省略 mRenderNode = RenderNode.create(getClass().getName(), this); //省略 } 复制代码

RenderNode通过调用静态方法create得到RenderNode对象,我们继续看RenderNode.create()方法

RenderNode.create()

/** * @param name The name of the RenderNode, used for debugging purpose. May be null. * @return A new RenderNode. */ public static RenderNode create(String name, @Nullable View owningView) { return new RenderNode(name, owningView); } 复制代码

create()方法有两个参数,第一个name,第二个是owningView,而且是可以为空的,从注释上来看,name只是为了调试用,而且owningView可以为空,我们可以用反射去创建一个简单的RenderNode;

尝试创建一个Canvas

回顾一下,写出一个简单的创建一个硬件加速Canvas的代码:

第一行,创建RenderNode RenderNode node = RenderNode.create("helloworld", null); 第二行,创建DisplayListCanvas final DisplayListCanvas canvas = node.start(bitmapWidth, bitmapHeight); 第三行,执行canvas的操作 canvas.xxx(); 第四行,执行node.end()方法 node.end(canvas); 复制代码

一个简单的DisplayListCanvas创建流程在脑海中浮现出来,但是还有个问题,我们执行完canvas的绘制操作之后,生成的产物Bitmap从哪里得到,我们回顾和ViewRootImpl打交道的硬件加速绘制相关的类是ThreadedRenderer,我们刚才看了这个类的draw()方法和updateRootDisplayList()方法,很有意思,它还有一个这个静态的方法createHardwareBitmap(RenderNode node, int width, int height);

ThreadedRenderer.createHardwareBitmap()

public static Bitmap createHardwareBitmap(RenderNode node, int width, int height) { return nCreateHardwareBitmap(node.getNativeDisplayList(), width, height); } 复制代码

该方法根据传入的RenderNode创建一个硬件加速的Bitmap并返回,要求传入的这个node必须是根root,在这里,一个完整的获取替换Canvas的流程应该是这样;

第一行,创建RenderNode RenderNode node = RenderNode.create("helloworld", null); 第二行,创建DisplayListCanvas final DisplayListCanvas canvas = node.start(width, height); 第三行,执行canvas的操作 canvas.xxx(); 第四行,执行node.end()方法 node.end(canvas); 第五行,调用createHardwareBitmap生成Bitmap bitmap = ThreadedRenderer.createHardwareBitmap(node,width,height) 复制代码

基于上面的伪代码分析,我写了一个避免反射调优化版的Hardware Canvas,基本调用如下:

//创建HardwareCanvasManager val hardwareCanvasManager = HardwareCanvasManager() try { //获取canvas val canvas = hardwareCanvasManager.createCanvas(size, size) //画圆形or其他绘制 canvas.drawCircle(radius, radius, radius, CIRCLE_CROP_SHAPE_PAINT); //画原图,通过画笔设置SRC_IN属性 canvas.drawBitmap(inBitmap, null, destRect, CIRCLE_CROP_BITMAP_PAINT); //得到bitmap val buildBitmap = hardwareCanvasManager.buildBitmap() //将bitmap设置给ImageView iv.setImageBitmap(buildBitmap) } finally { //清理工作 hardwareCanvasManager.clean() } 复制代码

Github传送门

总结

这篇水文主要是分析View绘制下Canvas的创建流程,关于硬件加速的更详细的介绍,推荐大家看这篇文章www.jianshu.com/p/40f660e17…。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有